package com.amadeus.spin.devtool.soap.shooter.process;

import java.io.IOException;
import java.io.StringReader;
import java.util.List;
import java.util.Stack;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.FactoryConfigurationError;
import javax.xml.parsers.ParserConfigurationException;

import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import com.amadeus.spin.devtool.soap.shooter.FatalException;
import com.amadeus.spin.devtool.soap.shooter.ProcessInput;
import com.amadeus.spin.devtool.soap.shooter.Shooter;
import com.amadeus.spin.standard.util.XPathHelper;

/**
 * ReplaceVariable.java
 *
 * See documentation for syntax
 *
 * Created: Fri Mar 26 13:04:45 2004
 *
 * @author <a href="mailto:pvoute@amadeus.net"></a>
 * @version 1.0
 */

public class ReplaceVariable implements ProcessInput {

  private static Logger log = Logger.getLogger(ReplaceVariable.class);

  public ReplaceVariable() {
  } // ReplaceVariable constructor

  /**
   * Extracts variables from the previous output and replace them in next input.
   *
   *
   * @param previousOutput
   *          a <code>String</code> value
   * @param input
   *          a <code>String</code> value
   * @param config
   *          a <code>Node</code> value
   * @return a <code>String</code> value
   * @exception FatalException
   *              if an error occurs
   */
  @Override
  public String processInput(Stack<String> previousOutput, String input, Node config) throws FatalException {
    List<String> keys = XPathHelper.getProperties(config, "entry/@key");
    List<String> values = XPathHelper.getProperties(config, "entry/@value");
    Document doc = null;
    for (int i = 0; i < keys.size(); i++) {
      String key = keys.get(i);
      List<String> transactions = XPathHelper.getProperties(config, "entry[@key='" + key + "']/@fromOutput");
      if (key.charAt(0) == '$') {
        String value = Shooter.replaceVariables(values.get(i));
        int transactionIndex = extractTransactionForKey(key, transactions);
        String valueReplacement = null;
        String infoMsg = null;
        String infoMsgSuffix = null;

        if (previousOutput != null && transactionIndex < previousOutput.size()) {
          doc = buildInput(previousOutput.get(previousOutput.size() - (1 + transactionIndex)));
          valueReplacement = XPathHelper.selectNodeValue(doc, value);

          infoMsg = "setting " + key + " = ";
          infoMsgSuffix = "found in previous output at XPath : " + value;
        }

        if (StringUtils.isEmpty(valueReplacement)) {
          infoMsgSuffix = "Could not reach defined XPath in previous output to replace " + key + ".";
          String defaultValueFromConfig = XPathHelper.getProperty(config, "entry[@key='" + key + "']/@valueifempty");
          if (defaultValueFromConfig != null) {
            infoMsgSuffix += " Replacing with value defined in <valueifempty> : " + defaultValueFromConfig;
            infoMsgSuffix = "";
            valueReplacement = defaultValueFromConfig;
          }
          else {
            infoMsgSuffix += " Replacing with empty string.";
            valueReplacement = "";
          }
        }
        else {
          Shooter.getVariables().put(key, valueReplacement);
        }
        infoMsg += "\"" + valueReplacement + "\"" + "(" + infoMsgSuffix + ")";
        log.info(infoMsg);

        input = inputWithKeyOccurrencesReplacedByValue(input, valueReplacement, key);
      }
    }
    return input;
  }

  /**
   * Evaluates the document value associated asked by the scenario
   *
   * @param doc
   * @param value
   * @return
   * @throws FatalException
   */
  protected String evaluateDocumentValue(Document doc, String value) throws FatalException {
    // String xPathEvaluation = null;
    // try {
    // xPathEvaluation = XPathAPI.eval(doc, value).toString();
    // }
    // catch (TransformerException e) {
    // throw new FatalException("XpathAPI.eval() call failed for query " + value, e);
    // }
    // return xPathEvaluation;
    //
    return XPathHelper.selectNodeValue(doc, value);
  }

  /**
   * Gets the transaction reference index from the document. Default transaction
   * index is 0 (previous one)
   *
   * @param key
   * @param outPuts
   * @return
   */
  private int extractTransactionForKey(String key, List<String> outPuts) {
    int fromOutput = 0;
    if (outPuts != null && outPuts.size() > 0) {
      try {
        fromOutput = Integer.valueOf(outPuts.get(0));
      }
      catch (NumberFormatException e) {
        log.warn("Param fromOutput set on key " + key + " is not a valid number");
      }
    }
    return fromOutput;
  }

  private String inputWithKeyOccurrencesReplacedByValue(String input, String xoValue, String key) {
    int index = -1;
    int start = 0;
    StringBuffer sb = new StringBuffer();
    while ((index = input.indexOf(key, start)) != -1) {
      sb.append(input.substring(start, index));
      sb.append(xoValue);
      start = index + key.length();
    }
    if (start < input.length()) {
      sb.append(input.substring(start));
    }
    return sb.toString();
  }

  private static Document buildInput(String input) throws FatalException {
    try {
      return DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(new InputSource(new StringReader(input)));
    }
    catch (SAXException e) {
      log.error("cannot build DOM from input");
      throw new FatalException("cannot build DOM from input", e);
    }
    catch (IOException e) {
      log.error("cannot build DOM from input");
      throw new FatalException("cannot build DOM from input", e);
    }
    catch (ParserConfigurationException e) {
      log.error("cannot build DOM from input");
      throw new FatalException("cannot build DOM from input", e);

    }
    catch (FactoryConfigurationError e) {
      log.error("cannot build DOM from input");
      throw new FatalException("cannot build DOM from input");
    }
  }
} // ReplaceVariable
